#ifndef _CIMAGEBLITTER_H_
#define _CIMAGEBLITTER_H_

#include "irrlicht.h"
#include "os.h"

namespace irr
{
namespace video
{

struct AbsRectangle
{
	s32 x0;
	s32 y0;
	s32 x1;
	s32 y1;
};

//! a more useful memset for pixel
inline void memset32 ( void * dest, const u32 value, u32 bytesize )
{
	u32 * d = (u32*) dest;

	u32 i;

	i = bytesize >> ( 2 + 3 );
	while( i )
	{
		d[0] = value;
		d[1] = value;
		d[2] = value;
		d[3] = value;

		d[4] = value;
		d[5] = value;
		d[6] = value;
		d[7] = value;

		d += 8;
		i -= 1;
	}

	i = (bytesize >> 2 ) & 7;
	while( i )
	{
		d[0] = value;
		d += 1;
		i -= 1;
	}

}

inline s32 s32_min ( s32 a, s32 b)
{
	s32 c = (a - b) >> 31;
	return (a & c) + (b & ~c);
}

inline s32 s32_max ( s32 a, s32 b)
{
	b = a - b;
	a -= b & (b>>31);
	return a;
}

inline s32 s32_clamp (s32 value, s32 low, s32 high)
{
	return s32_min (s32_max(value,low), high);
}

/*
   	Pixel = c0 * (c1/31). c0 Alpha retain
 */
inline u16 PixelMul16 ( u16 c0, u16 c1)
{
	return ( ( (c0 & 0x7C00) * (c1 & 0x7C00) ) & 0x3E000000 ) >> 15 |
	       ( ( (c0 & 0x03E0) * (c1 & 0x03E0) ) & 0x000F8000 ) >> 10 |
	       ( ( (c0 & 0x001F) * (c1 & 0x001F) ) & 0x000003E0 ) >> 5  |
	       c0 & 0x8000;
}


inline u32 extractAlpha ( u32 c )
{
	return ( c >> 24 ) + ( c >> 31 );
}

/*
   	Pixel = c0 * (c1/255). c0 Alpha Retain
 */
inline u32 PixelMul32 ( u32 c0, u32 c1)
{
	return c0 & 0xFF000000 |
	       ( ( (c0 & 0x00FF0000) >> 12 ) * ( (c1 & 0x00FF0000) >> 12 ) ) & 0x00FF0000 |
	       ( ( (c0 & 0x0000FF00) * (c1 & 0x0000FF00) ) >> 16 ) & 0x0000FF00 |
	       ( ( (c0 & 0x000000FF) * (c1 & 0x000000FF) ) >> 8  ) & 0x000000FF;
}


// 1 - Bit Alpha Blending
inline u16 PixelBlend16 ( const u16 c2, const u16 c1 )
{
	u16 c = c1 & 0x8000;

	c >>= 15;
	c += 0x7fff;

	c &= c2;
	c |= c1;

	return c;
}

/*!
   	Pixel = dest * ( 1 - alpha ) + source * alpha
   	alpha [0;32]
 */
inline u16 PixelBlend16 ( const u16 c2, const u32 c1, const u32 alpha )
{
	u32 srcRB = c1 & 0x7C1F;
	u32 srcXG = c1 & 0x03E0;

	u32 dstRB = c2 & 0x7C1F;
	u32 dstXG = c2 & 0x03E0;


	u32 rb = srcRB - dstRB;
	u32 xg = srcXG - dstXG;

	rb *= alpha;
	xg *= alpha;
	rb >>= 5;
	xg >>= 5;

	rb += dstRB;
	xg += dstXG;

	rb &= 0x7C1F;
	xg &= 0x03E0;

	return rb | xg;
}

/*!
   	Pixel = dest * ( 1 - alpha ) + source * alpha
   	alpha [0;256]
 */
inline u32 PixelBlend32 ( const u32 c2, const u32 c1, u32 alpha )
{
	u32 srcRB = c1 & 0x00FF00FF;
	u32 srcXG = c1 & 0x0000FF00;

	u32 dstRB = c2 & 0x00FF00FF;
	u32 dstXG = c2 & 0x0000FF00;


	u32 rb = srcRB - dstRB;
	u32 xg = srcXG - dstXG;

	rb *= alpha;
	xg *= alpha;
	rb >>= 8;
	xg >>= 8;

	rb += dstRB;
	xg += dstXG;

	rb &= 0x00FF00FF;
	xg &= 0x0000FF00;

	return rb | xg;
}

// 1 - Bit Alpha Blending 16Bit SIMD
inline u32 PixelBlend16_simd ( const u32 c2, const u32 c1 )
{
	u32 c = c1 & 0x80008000;

	c >>= 15;
	c += 0x7fff7fff;

	c &= c2;
	c |= c1;

	return c;
}


/*!
   	Pixel = dest * ( 1 - SourceAlpha ) + source * SourceAlpha
 */
inline u32 PixelBlend32 ( const u32 c2, const u32 c1 )
{
	// alpha test
	u32 alpha = c1 & 0xFF000000;

	if ( 0 == alpha )
		return c2;

	if ( 0xFF000000 == alpha )
	{
		return c1;
	}

	alpha >>= 24;

	// add highbit alpha, if ( alpha > 127 ) alpha += 1;
	alpha += ( alpha >> 7);

	u32 srcRB = c1 & 0x00FF00FF;
	u32 srcXG = c1 & 0x0000FF00;

	u32 dstRB = c2 & 0x00FF00FF;
	u32 dstXG = c2 & 0x0000FF00;


	u32 rb = srcRB - dstRB;
	u32 xg = srcXG - dstXG;

	rb *= alpha;
	xg *= alpha;
	rb >>= 8;
	xg >>= 8;

	rb += dstRB;
	xg += dstXG;

	rb &= 0x00FF00FF;
	xg &= 0x0000FF00;

	return rb | xg;
}

inline void intersect ( AbsRectangle &dest, const AbsRectangle& a, const AbsRectangle& b)
{
	dest.x0 = s32_max( a.x0, b.x0 );
	dest.y0 = s32_max( a.y0, b.y0 );
	dest.x1 = s32_min( a.x1, b.x1 );
	dest.y1 = s32_min( a.y1, b.y1 );
}

inline bool isValid (const AbsRectangle& a)
{
	return a.x0 < a.x1 && a.y0 < a.y1;
}


struct SBlitJob
{
	AbsRectangle Dest;
	AbsRectangle Source;

	u32 argb;

	void * src;
	void * dst;

	s32 width;
	s32 height;

	u32 srcPitch;
	u32 dstPitch;

	u32 srcPixelMul;
	u32 dstPixelMul;
};

// Blitter Operation
enum eBlitter
{
	BLITTER_INVALID = 0,
	BLITTER_COLOR,
	BLITTER_COLOR_ALPHA,
	BLITTER_TEXTURE,
	BLITTER_TEXTURE_ALPHA_BLEND,
	BLITTER_TEXTURE_ALPHA_COLOR_BLEND
};

typedef void (*tExecuteBlit)( const SBlitJob * job );


// Bitfields Cohen Sutherland
enum eClipCode
{
	CLIPCODE_EMPTY  =       0,
	CLIPCODE_BOTTOM =       1,
	CLIPCODE_TOP    =       2,
	CLIPCODE_LEFT   =       4,
	CLIPCODE_RIGHT  =       8
};

inline u32 GetClipCode ( const AbsRectangle &r, const core::position2d<s32> &p )
{
	u32 code = CLIPCODE_EMPTY;

	if ( p.X < r.x0 )
		code = CLIPCODE_LEFT;
	else
	if ( p.X > r.x1 )
		code = CLIPCODE_RIGHT;

	if ( p.Y < r.y0 )
		code |= CLIPCODE_TOP;
	else
	if ( p.Y > r.y1 )
		code |= CLIPCODE_BOTTOM;

	return code;
}

inline void GetClip ( AbsRectangle &clipping, video::IImage * t)
{
	clipping.x0 = 0;
	clipping.y0 = 0;
	clipping.x1 = t->getDimension().Width - 1;
	clipping.y1 = t->getDimension().Height - 1;
}

void executeBlit_TextureCopy_x_to_x ( const SBlitJob * job )
{
	void *src = (void*) job->src;
	void *dst = (void*) job->dst;

	const u32 widthPitch = job->width * job->dstPixelMul;
	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		memcpy ( dst, src, widthPitch );

		src = (void*) ( (u8*) (src) + job->srcPitch );
		dst = (void*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureCopy_32_to_16 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u16 *dst = (u16*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = video::A8R8G8B8toA1R5G5B5 ( src[dx] );
		}

		src = (u32*) ( (u8*) (src) + job->srcPitch );
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureCopy_24_to_16 ( const SBlitJob * job )
{
	void *src = (void*) job->src;
	u16 *dst = (u16*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		u8 * s = (u8*) src;

		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = video::RGB16(s[0], s[1], s[2]);
			s += 3;
		}

		src = (void*) ( (u8*) (src) + job->srcPitch );
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}


/*!
 */
void executeBlit_TextureCopy_16_to_32 ( const SBlitJob * job )
{
	u16 *src = (u16*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = video::A1R5G5B5toA8R8G8B8 ( src[dx] );
		}

		src = (u16*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureCopy_24_to_32 ( const SBlitJob * job )
{
	void *src = (void*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		u8 * s = (u8*) src;

		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = s[0] << 16 | s[1] << 8 | s[2];
			s += 3;
		}

		src = (void*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}


/*!
 */
void executeBlit_TextureBlend_16_to_16 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u32 *dst = (u32*) job->dst;

	u32 rdx = job->width >> 1;
	u32 off = job->width & 1 ? (job->width - 1 ) : 0;

	if ( 0 == off )
	{
		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			for ( u32 dx = 0; dx != rdx; ++dx )
			{
				dst[dx] = PixelBlend16_simd ( dst[dx], src[dx] );
			}

			src = (u32*) ( (u8*) (src) + job->srcPitch );
			dst = (u32*) ( (u8*) (dst) + job->dstPitch );
		}

	}
	else
	{
		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			for ( u32 dx = 0; dx != rdx; ++dx )
			{
				dst[dx] = PixelBlend16_simd ( dst[dx], src[dx] );
			}

			((u16*) dst)[off] = PixelBlend16 ( ((u16*) dst)[off], ((u16*) src)[off] );

			src = (u32*) ( (u8*) (src) + job->srcPitch );
			dst = (u32*) ( (u8*) (dst) + job->dstPitch );
		}

	}
}

/*!
 */
void executeBlit_TextureBlend_32_to_32 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend32 ( dst[dx], src[dx] );
		}
		src = (u32*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureBlendColor_16_to_16 ( const SBlitJob * job )
{
	u16 *src = (u16*) job->src;
	u16 *dst = (u16*) job->dst;

	u16 blend = video::A8R8G8B8toA1R5G5B5 ( job->argb );
	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend16 ( dst[dx], PixelMul16 ( src[dx], blend ) );
		}
		src = (u16*) ( (u8*) (src) + job->srcPitch );
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}


/*!
 */
void executeBlit_TextureBlendColor_32_to_32 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend32 ( dst[dx], PixelMul32 ( src[dx], job->argb ) );
		}
		src = (u32*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_Color_16_to_16 ( const SBlitJob * job )
{
	u16 *dst = (u16*) job->dst;

	u32 c0 = video::A8R8G8B8toA1R5G5B5 ( job->argb );
	u32 c = c0 | c0 << 16;

	if ( 0 == (job->srcPitch & 3 ) )
	{
		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			memset32 ( dst, c, job->srcPitch );
			dst = (u16*) ( (u8*) (dst) + job->dstPitch );
		}
	}
	else
	{
		s32 dx = job->width - 1;

		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			memset32 ( dst, c, job->srcPitch );
			dst[dx] = c0;
			dst = (u16*) ( (u8*) (dst) + job->dstPitch );
		}

	}
}

/*!
 */
void executeBlit_Color_32_to_32 ( const SBlitJob * job )
{
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		memset32 ( dst, job->argb, job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_ColorAlpha_16_to_16 ( const SBlitJob * job )
{
	u16 *dst = (u16*) job->dst;

	u32 alpha = extractAlpha ( job->argb ) >> 3;
	u32 src = video::A8R8G8B8toA1R5G5B5 ( job->argb );

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend16 ( dst[dx], src, alpha );
		}
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_ColorAlpha_32_to_32 ( const SBlitJob * job )
{
	u32 *dst = (u32*) job->dst;

	u32 alpha = extractAlpha ( job->argb );
	u32 src = job->argb;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend32 ( dst[dx], src, alpha );
		}
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
tExecuteBlit getBlitter ( eBlitter operation,video::IImage * dest,video::IImage * source )
{
	video::ECOLOR_FORMAT sourceFormat = (video::ECOLOR_FORMAT)-1;
	video::ECOLOR_FORMAT destFormat = (video::ECOLOR_FORMAT)-1;

	if ( source )
		sourceFormat = source->getColorFormat();
	if ( dest )
		destFormat = dest->getColorFormat();

	switch ( operation )
	{
	case BLITTER_TEXTURE:
	{
		if ( sourceFormat == destFormat )
			return executeBlit_TextureCopy_x_to_x;

		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_A8R8G8B8 )
			return executeBlit_TextureCopy_32_to_16;

		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_R8G8B8 )
			return executeBlit_TextureCopy_24_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_A1R5G5B5 )
			return executeBlit_TextureCopy_16_to_32;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_R8G8B8 )
			return executeBlit_TextureCopy_24_to_32;

	} break;

	case BLITTER_TEXTURE_ALPHA_BLEND:
	{
		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_A1R5G5B5 )
			return executeBlit_TextureBlend_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_A8R8G8B8 )
			return executeBlit_TextureBlend_32_to_32;

	} break;

	case BLITTER_TEXTURE_ALPHA_COLOR_BLEND:
	{
		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_A1R5G5B5 )
			return executeBlit_TextureBlendColor_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_A8R8G8B8 )
			return executeBlit_TextureBlendColor_32_to_32;
	} break;

	case BLITTER_COLOR:
	{
		if ( destFormat == video::ECF_A1R5G5B5 )
			return executeBlit_Color_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 )
			return executeBlit_Color_32_to_32;
	} break;

	case BLITTER_COLOR_ALPHA:
	{
		if ( destFormat == video::ECF_A1R5G5B5 )
			return executeBlit_ColorAlpha_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 )
			return executeBlit_ColorAlpha_32_to_32;

	} break;

	}

	char buf[64];
	sprintf ( buf, "Blit: %d %d->%d unsupported",operation,sourceFormat,destFormat );
	os::Printer::log (buf );
	return 0;

}



/*!
   	a generic 2D Blitter
 */
s32 Blit (      eBlitter operation,
                video::IImage * dest,
                const core::rect<s32> *destClipping,
                const core::position2d<s32> *destPos,
                video::IImage * source,
                const core::rect<s32> *sourceClipping,
                u32 argb
                )
{
	tExecuteBlit blitter = getBlitter ( operation, dest, source );
	if ( 0 == blitter )
	{
		return 0;
	}

	// Clipping
	AbsRectangle sourceClip;
	AbsRectangle destClip;
	AbsRectangle v;

	SBlitJob job;

	if ( sourceClipping )
	{
		sourceClip.x0 = sourceClipping->UpperLeftCorner.X;
		sourceClip.y0 = sourceClipping->UpperLeftCorner.Y;
		sourceClip.x1 = sourceClipping->LowerRightCorner.X;
		sourceClip.y1 = sourceClipping->LowerRightCorner.Y;
	}
	else
	{
		sourceClip.x0 = 0;
		sourceClip.y0 = 0;
		sourceClip.x1 = source ? source->getDimension().Width : 0;
		sourceClip.y1 = source ? source->getDimension().Height : 0;
	}

	if ( destClipping )
	{
		destClip.x0 = destClipping->UpperLeftCorner.X;
		destClip.y0 = destClipping->UpperLeftCorner.Y;
		destClip.x1 = destClipping->LowerRightCorner.X;
		destClip.y1 = destClipping->LowerRightCorner.Y;
	}
	else
	{
		destClip.x0 = 0;
		destClip.y0 = 0;
		destClip.x1 = dest ? dest->getDimension().Width : 0;
		destClip.y1 = dest ? dest->getDimension().Height : 0;
	}

	v.x0 = destPos ? destPos->X : 0;
	v.y0 = destPos ? destPos->Y : 0;
	v.x1 = v.x0 + ( sourceClip.x1 - sourceClip.x0 );
	v.y1 = v.y0 + ( sourceClip.y1 - sourceClip.y0 );

	intersect ( job.Dest, destClip, v );
	if ( !isValid ( job.Dest ) )
		return 0;

	job.width = job.Dest.x1 - job.Dest.x0;
	job.height = job.Dest.y1 - job.Dest.y0;


	job.Source.x0 = sourceClip.x0 + ( job.Dest.x0 - v.x0 );
	job.Source.x1 = job.Source.x0 + job.width;

	job.Source.y0 = sourceClip.y0 + ( job.Dest.y0 - v.y0 );
	job.Source.y1 = job.Source.y0 + job.height;

	job.argb = argb;

	if ( source )
	{
		job.srcPitch = source->getPitch();
		job.srcPixelMul = source->getBytesPerPixel();
		job.src = (void*) ( (u8*) source->lock() + ( job.Source.y0 * job.srcPitch ) + ( job.Source.x0 * job.srcPixelMul ) );
	}
	else
	{
		// use srcPitch for color operation on dest
		job.srcPitch = job.width * dest->getBytesPerPixel();
	}

	job.dstPitch = dest->getPitch();
	job.dstPixelMul = dest->getBytesPerPixel();
	job.dst = (void*) ( (u8*) dest->lock() + ( job.Dest.y0 * job.dstPitch ) + ( job.Dest.x0 * job.dstPixelMul ) );

	blitter ( &job );

	if ( source )
		source->unlock ();

	if ( dest )
		dest->unlock();

	return 1;
}
}
}

#endif